feat: redesign testimonial marquee with manual controls, add security,headers, and fix SEO issues#335
Conversation
… headers, and fix SEO issues Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
… issues Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
Code Review SummaryStatus: No Issues Found | Recommendation: Merge OverviewThis PR includes several quality improvements:
Files Reviewed (6 files)
|
Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
|
hey @amaan-bhati i have proposed a solution to the given issue of the testimonial marquee which is redesigned to the design which you have mentioned in the issue |
|
Hi @HEETMEHTA18. Feel free to look into other open issues in the repository and pick one that hasn’t been addressed yet. We’d be happy to review another contribution from you. Thanks again for your effort! |
|
@dhananjay6561 however but still in the website no furthur changes are made till i just made changes into this so that the avatar was not loading but in the given pr the changes are made. |
Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
|
Hey @HEETMEHTA18 👋 — thanks for putting this PR together, we appreciate the effort! We've gone ahead and requested a Copilot review on this. Here's some context from the reviewer:
Once you've had a chance to go through the comments, please address the feedback and resolve the threads — and we'll get this across the line. Feel free to ask if anything's unclear. Happy coding! 💙 |
There was a problem hiding this comment.
Pull request overview
This PR modernizes the blog landing page experience by redesigning the testimonials marquee, tightening security headers at the Next.js edge, and fixing SEO-related asset paths under the /blog basePath.
Changes:
- Replaced the legacy testimonials marquee with a dual-row, hover-pausing infinite scroll layout (with a skeleton fallback during initial mount).
- Added standard security headers (
X-Frame-Options,X-Content-Type-Options,Referrer-Policy,Permissions-Policy) vianext.config.js. - Fixed SEO/meta asset URLs to be basePath-correct and cleaned up SEO token/typo issues on the homepage.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
pages/index.tsx |
Removes inline <Head> title usage (relying on Layout/Meta) and fixes a typo in technology posts variable naming. |
next.config.js |
Adds additional security headers alongside the existing CSP. |
components/testimonials.tsx |
Implements the redesigned dual-row marquee testimonial UI and avatar proxy/fallback handling. |
components/meta.tsx |
Corrects favicon/manifest/RSS/browserconfig paths to work under /blog basePath. |
components/footer.tsx |
Adds aria-hidden to decorative social SVG icons. |
components/cover-image.tsx |
Adds safer fallbacks for alt/src and refactors className handling (but currently introduces a critical build issue). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| className={cn("rounded-md transition-border duration-300", imgClassName, { | ||
| " transition-scale duration-300": slug, | ||
| })} |
There was a problem hiding this comment.
CoverImage now uses cn(...) but the helper isn’t imported in this file, which will cause a runtime/compile failure. Also, the Tailwind classes transition-border and transition-scale don’t exist in this repo’s Tailwind config, so the intended transitions won’t apply; use valid Tailwind transition utilities (or extend Tailwind) instead.
| className={cn("rounded-md transition-border duration-300", imgClassName, { | |
| " transition-scale duration-300": slug, | |
| })} | |
| className={[ | |
| "rounded-md transition-colors duration-300", | |
| slug ? "transition-transform duration-300" : "", | |
| imgClassName ?? "", | |
| ] | |
| .filter(Boolean) | |
| .join(" ")} |
| /** Fallback avatar component – renders a user icon/initials in a branded circle */ | ||
| const FallbackAvatar = ({ name }: { name: string }) => ( | ||
| <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1"> | ||
| <User className="h-full w-full opacity-60" /> | ||
| </div> | ||
| ); | ||
|
|
There was a problem hiding this comment.
getInitials is declared but never used, and FallbackAvatar accepts a name prop but doesn’t render initials (only the icon). This is dead code / misleading intent—either remove the unused helper/prop or render initials as the fallback so the behavior matches the comment.
| /** Fallback avatar component – renders a user icon/initials in a branded circle */ | |
| const FallbackAvatar = ({ name }: { name: string }) => ( | |
| <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1"> | |
| <User className="h-full w-full opacity-60" /> | |
| </div> | |
| ); | |
| /** Fallback avatar component – renders initials when available, otherwise a user icon */ | |
| const FallbackAvatar = ({ name }: { name: string }) => { | |
| const initials = name.trim() ? getInitials(name) : ""; | |
| return ( | |
| <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-2 ring-gray-100 p-1"> | |
| {initials ? ( | |
| <span className="text-sm font-medium leading-none">{initials}</span> | |
| ) : ( | |
| <User className="h-full w-full opacity-60" /> | |
| )} | |
| </div> | |
| ); | |
| }; |
| return ( | ||
|
|
||
| <Layout | ||
| preview={preview} | ||
| featuredImage={HOME_OG_IMAGE_URL} | ||
| Title={`Blog - Keploy`} | ||
| Description={"The Keploy Blog offers in-depth articles and expert insights on software testing, automation, and quality assurance, empowering developers to enhance their testing strategies and deliver robust applications."} | ||
| structuredData={structuredData} | ||
| > | ||
| <Head> | ||
| <title>{`Engineering | Keploy Blog`}</title> | ||
| </Head> | ||
| <Header /> | ||
| <Container> |
There was a problem hiding this comment.
After removing the inline <Head> usage, the Head import at the top of this file is now unused and will be flagged by next lint. Please remove the unused import to keep CI/lint clean.
Signed-off-by: Heet Mehta <heetmehta18125@gmail.com>
There was a problem hiding this comment.
QA Report — feat: redesign testimonial marquee with manual controls, add security headers, and fix SEO issues
Repo: keploy/blog-website | Author: HEETMEHTA18 | Review date: 2026-04-30
Change type: content-rendering-change, config-change, metadata-or-seo-change
Summary
| Severity | Count |
|---|---|
| 🔴 Blocking | 3 |
| 🟡 Warning | 4 |
| 🔵 Suggestion | 4 |
| ⚪ Nitpick | 3 |
Reviewed files: components/cover-image.tsx, components/footer.tsx, components/meta.tsx, components/testimonials.tsx, next.config.js, pages/index.tsx
Gate checklist
- No blocking findings remain
- Metadata contract still holds on production pages (see title warning)
- Base-path and route integrity remain valid
- Impacted files were checked for regressions
🔴 Blocking
-
components/cover-image.tsx—cnused but never imported
Why: The PR replaces the inline className template literal withcn(...)but adds no import forcn. This is a build-breakingReferenceError— the site will not compile.
Fix:import { cn } from "@/lib/utils"; // verify path under the @/* alias
-
components/testimonials.tsx:~87— Raw<img>tag instead ofnext/image
Why: Framework rule #3 blocks new raw<img>tags in component code. The newReviewCarduses<img>for avatars, bypassing Next.js image optimization (format selection, lazy loading queue, responsive srcsets). Thepages/api/proxy-image.tsproxy already exists, sonext/imagewith the proxy URL is the correct pattern.
Fix:import NextImage from "next/image"; // … <NextImage className="rounded-full object-cover ring-2 ring-primary-100" width={40} height={40} alt={name ? `${name}'s avatar` : "Avatar"} src={proxiedAvatar} onError={() => setImgError(true)} />
-
components/cover-image.tsx:~32—priorityhardcoded totruefor all cover images
Why:priority={priority}(caller-controlled, defaulting tofalse) was replaced with the barepriorityliteral (alwaystrue). Every cover image sitewide — including list-page thumbnails — will now be eagerly preloaded, regressing Lighthouse CWV scores (TBT, FCP, TTI).
Fix: Restore the conditional props:priority={priority} loading={priority ? "eager" : "lazy"} sizes={sizes}
🟡 Warnings
-
components/cover-image.tsx—sizesprop silently dropped
Why:sizesis still declared in thePropsinterface and destructured, but the new<Image>render no longer passes it. This silently removes the responsive image hint callers relied on for bandwidth-optimal image selection.
Fix: Restoresizes={sizes}to the<Image>element, or removesizesfrom the interface if it is intentionally deprecated. -
components/meta.tsx:40-41—Group.svgdoes not exist inpublic/favicon/
Why: The fork'spublic/favicon/directory containssafari-pinned-tab.svgbut notGroup.svg(confirmed by directory listing). The<link rel="mask-icon">will 404 in Safari on every page globally. (Group.pngdoes exist — the shortcut-icon change is fine.)
Fix: Revert the mask-icon line:<link rel="mask-icon" href="/blog/favicon/safari-pinned-tab.svg" color="#000000" />
…or add
Group.svgtopublic/favicon/before merging. -
pages/index.tsx:36-38— Route-specific<title>removed; title coverage unverified
Why: The<Head><title>{Engineering | Keploy Blog}</title></Head>block is removed. TheMetacomponent (reviewed in full) does not emit a<title>tag — only OG, Twitter, description, and canonical meta. IfLayoutdoes not emit<title>from theTitleprop, the homepage will have no title, which is a critical SEO regression.
Fix: Either restore the route-specific<title>(framework rule #2 explicitly permits this forpages/index.tsx), or document thatlayout.tsxemits<title>from theTitleprop and verify it in the browser before merging. -
components/testimonials.tsx—useRoutercalled perReviewCardinstance
Why:ReviewCardis rendered for every tweet (20+ cards).useRouter()inside each card creates per-instance router subscriptions. The only value consumed —router.basePath— is a static build-time constant that never changes at runtime.
Fix: ResolvebasePathonce inTwitterTestimonialsand pass it as a prop toReviewCard.
🔵 Suggestions
-
components/testimonials.tsx:13-17—getInitials()is defined with a JSDoc comment but never called.FallbackAvatarshows theUsericon instead. Either usegetInitials(name)for initials display insideFallbackAvatar, or delete the function. -
components/testimonials.tsx:56-62— TheuseEffectthat resetsimgErroron avatar prop change is unnecessary. Prop changes already trigger a re-render; the effect adds an extra render cycle with no real benefit. Remove it, or usekey={avatar}onReviewCardat the call site for a cleaner reset. -
components/testimonials.tsx:115-145— Themountedguard renders a skeleton on SSR and replaces it with the marquee on the client, causing measurable CLS. Consider usingopacity-0/visibility: hiddenon the SSR pass and fading in on mount to avoid the layout shift. -
next.config.js—X-Frame-Options: DENYis present but the CSP is missingframe-ancestors 'none'. Modern browsers prefer the CSP directive; adding both provides defense-in-depth for legacy and modern browsers alike.
⚪ Nitpicks
-
components/testimonials.tsx:7—import { User } from "lucide-react"is placed afterimport Tweets from "../services/Tweets", breaking conventional import grouping (external packages before internal services). Move it with the other third-party imports at the top. -
components/cover-image.tsx:35— Double leading space in" transition-scale duration-300". -
components/cover-image.tsx:34-36—transition-borderandtransition-scaleare not valid Tailwind CSS utilities. Standard Tailwind includestransition-colorsandtransition-transform; the non-existent classes will produce no CSS output.
✅ Looks Good
- Security headers (
X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin,Permissions-Policy) are well-chosen and do not conflict with the existing CSP. pages/index.tsxtypo fix:allTehcnologyPosts→allTechnologyPostsis a clean correction with no regressions.components/footer.tsxaccessibility:aria-hidden="true"on all four decorative SVG icons is correct — they are purely visual.- Marquee key uniqueness: Appending
-r1/-r2to tweet IDs correctly prevents duplicate React key warnings. - Proxy routing: External avatar URLs routed through
pages/api/proxy-image.tsfollows the established allowlist pattern for this repo. meta.tsxbase-path correctness: All favicon hrefs retain the/blog/prefix, consistent withbasePathconfig.- Duplicate
twitter:imagecleaned up: The pre-featuredImage-conditional duplicate meta tag is removed; the consolidated structure is cleaner.
Cross-file impact
Direct findings:
components/cover-image.tsx— brokencnimport blocks the build;priorityregression affects all post-list and post-detail pagescomponents/testimonials.tsx— raw<img>bypasses the Next.js image pipeline for all testimonial avatarscomponents/meta.tsx—Group.svg404 affects the Safari pinned-tab icon on every page (global)pages/index.tsx— removed<title>may leave the homepage without a title ifLayoutdoes not cover it
Inferred impact risks checked:
components/layout.tsx— not in diff;Metaconfirmed not to emit<title>; title coverage bylayout.tsxrequires manual verificationpages/api/proxy-image.ts— confirmed present in base repo; proxy routing in testimonials is safe
Overall verdict
Do not merge in current state. Three blockers must be resolved first: the missing cn import (build failure), the raw <img> tag (framework rule violation), and the hardcoded priority={true} regression (performance). Confirm the homepage <title> is still emitted and fix the Group.svg 404 before this ships. The security header additions, footer accessibility improvements, and typo fix are all clean — once the blockers are addressed this PR will be in good shape.
Review generated by Keploy QA Agent · 2026-04-30

Related Tickets & Documents
Fixes: #3440
Description
This PR revamps the blog landing page’s testimonial section to provide a "flawless" user experience, addressing community feedback regarding UI aesthetics and security. It replaces the legacy marquee with a modern dual-row scrolling layout.
GDG CHARUSAT TEAM ID:
<Team 143>Changes
X-Frame-Options,X-Content-Type-Options,Referrer-Policy,Permissions-Policy) innext.config.js.meta.tsxto ensure they load correctly under the/blogbase path.pages/index.tsx.Type of Change
Testing
npm run dev.npm run buildto ensure no production regressions or hydration errors.Demo
/blogsubdirectory.-Before:
Engineering._.Keploy.Blog.-.Google.Chrome.2026-02-24.21-44-31.mp4
After:
localhost_3000_blog.-.Google.Chrome.2026-02-24.21-51-45.mp4
Environment and Dependencies
next.config.jsto include standard security headers.Checklist